#include "U2RenderTarget.h"

#include "U2StringConverter.h"
#include "U2Exception.h"
#include "U2LogManager.h"
#include "U2RenderTargetListener.h"
#include "U2Root.h"
#include "U2RenderSystem.h"
#include "U2DepthBuffer.h"
#include "U2Viewport.h"
#include "U2Camera.h"



U2EG_NAMESPACE_USING



U2RenderTarget::U2RenderTarget()
: mPriority(U2_DEFAULT_RT_GROUP)
, mDepthBufferPoolId(U2DepthBuffer::POOL_DEFAULT)
, mDepthBuffer(0)
, mActive(true)
, mAutoUpdate(true)
, mHwGamma(false)
, mFSAA(0)
{
    mTimer = U2Root::getSingleton().getTimer();
    resetStatistics();
}

U2RenderTarget::~U2RenderTarget()
{
    // Delete viewports
    for (ViewportList::iterator i = mViewportList.begin();
        i != mViewportList.end(); ++i)
    {
        fireViewportRemoved(i->second);
        U2_DELETE (*i).second;
    }

	//DepthBuffer keeps track of us, avoid a dangling pointer
	detachDepthBuffer();


    // Write closing message
	U2LogManager::getSingleton().stream(LML_TRIVIAL)
		<< "Render Target '" << mName << "' "
		<< "Average FPS: " << mStats.avgFPS << " "
		<< "Best FPS: " << mStats.bestFPS << " "
		<< "Worst FPS: " << mStats.worstFPS; 
}

const U2String& U2RenderTarget::getName(void) const
{
    return mName;
}


void U2RenderTarget::getMetrics(unsigned int& width, unsigned int& height, unsigned int& colourDepth)
{
    width = mWidth;
    height = mHeight;
    colourDepth = mColourDepth;
}

unsigned int U2RenderTarget::getWidth(void) const
{
    return mWidth;
}
unsigned int U2RenderTarget::getHeight(void) const
{
    return mHeight;
}
unsigned int U2RenderTarget::getColourDepth(void) const
{
    return mColourDepth;
}
//-----------------------------------------------------------------------
void U2RenderTarget::setDepthBufferPool( u2uint16 poolId )
{
	if( mDepthBufferPoolId != poolId )
	{
		mDepthBufferPoolId = poolId;
		detachDepthBuffer();
	}
}
//-----------------------------------------------------------------------
u2uint16 U2RenderTarget::getDepthBufferPool() const
{
	return mDepthBufferPoolId;
}
//-----------------------------------------------------------------------
U2DepthBuffer* U2RenderTarget::getDepthBuffer() const
{
	return mDepthBuffer;
}
//-----------------------------------------------------------------------
bool U2RenderTarget::attachDepthBuffer( U2DepthBuffer *depthBuffer )
{
	bool retVal = false;

	if( (retVal = depthBuffer->isCompatible( this )) )
	{
		detachDepthBuffer();
		mDepthBuffer = depthBuffer;
		mDepthBuffer->_notifyRenderTargetAttached( this );
	}

	return retVal;
}
//-----------------------------------------------------------------------
void U2RenderTarget::detachDepthBuffer()
{
	if( mDepthBuffer )
	{
		mDepthBuffer->_notifyRenderTargetDetached( this );
		mDepthBuffer = 0;
	}
}
//-----------------------------------------------------------------------
void U2RenderTarget::_detachDepthBuffer()
{
	mDepthBuffer = 0;
}

void U2RenderTarget::updateImpl(void)
{
	_beginUpdate();
	_updateAutoUpdatedViewports(true);
	_endUpdate();
}

void U2RenderTarget::_beginUpdate()
{
	// notify listeners (pre)
    firePreUpdate();

    mStats.triangleCount = 0;
    mStats.batchCount = 0;
}

void U2RenderTarget::_updateAutoUpdatedViewports(bool updateStatistics)
{
	// Go through viewports in Z-order
    // Tell each to refresh
	ViewportList::iterator it = mViewportList.begin();
    while (it != mViewportList.end())
    {
		U2Viewport* viewport = (*it).second;
		if(viewport->isAutoUpdated())
		{
			_updateViewport(viewport,updateStatistics);
		}
		++it;
	}
}

void U2RenderTarget::_endUpdate()
{
	 // notify listeners (post)
    firePostUpdate();

    // Update statistics (always on top)
    updateStats();
}

void U2RenderTarget::_updateViewport(U2Viewport* viewport, bool updateStatistics)
{
	assert(viewport->getTarget() == this &&
			"U2RenderTarget::_updateViewport the requested viewport is "
			"not bound to the rendertarget!");

	fireViewportPreUpdate(viewport);
	viewport->update();
	if(updateStatistics)
	{
		mStats.triangleCount += viewport->_getNumRenderedFaces();
		mStats.batchCount += viewport->_getNumRenderedBatches();
	}
	fireViewportPostUpdate(viewport);
}

void U2RenderTarget::_updateViewport(int zorder, bool updateStatistics)
{
	ViewportList::iterator it = mViewportList.find(zorder);
    if (it != mViewportList.end())
    {
		_updateViewport((*it).second,updateStatistics);
	}
	else
	{
		U2_EXCEPT(U2Exception::ERR_ITEM_NOT_FOUND,"No viewport with given zorder : "
			+ U2StringConverter::toString(zorder), "U2RenderTarget::_updateViewport");
	}
}

U2Viewport* U2RenderTarget::addViewport(U2Camera* cam, int ZOrder, float left, float top ,
    float width , float height)
{		
    // Check no existing viewport with this Z-order
    ViewportList::iterator it = mViewportList.find(ZOrder);

    if (it != mViewportList.end())
    {
		U2StringUtil::U2StrStreamType str;
		str << "Can't create another viewport for "
			<< mName << " with Z-Order " << ZOrder
			<< " because a viewport exists with this Z-Order already.";
		U2_EXCEPT(U2Exception::ERR_INVALIDPARAMS, str.str(), "U2RenderTarget::addViewport");
    }
    // Add viewport to list
    // Order based on Z-Order
    U2Viewport* vp = U2_NEW U2Viewport(cam, this, left, top, width, height, ZOrder);

    mViewportList.insert(ViewportList::value_type(ZOrder, vp));

	fireViewportAdded(vp);

    return vp;
}
//-----------------------------------------------------------------------
void U2RenderTarget::removeViewport(int ZOrder)
{
    ViewportList::iterator it = mViewportList.find(ZOrder);

    if (it != mViewportList.end())
    {
		fireViewportRemoved((*it).second);
        U2_DELETE (*it).second;
        mViewportList.erase(ZOrder);
    }
}

void U2RenderTarget::removeAllViewports(void)
{
    for (ViewportList::iterator it = mViewportList.begin(); it != mViewportList.end(); ++it)
    {
        fireViewportRemoved(it->second);
        U2_DELETE (*it).second;
    }

    mViewportList.clear();
}

void U2RenderTarget::getStatistics(float& lastFPS, float& avgFPS,
    float& bestFPS, float& worstFPS) const
{
    // Note - the will have been updated by the last render
    lastFPS = mStats.lastFPS;
    avgFPS = mStats.avgFPS;
    bestFPS = mStats.bestFPS;
    worstFPS = mStats.worstFPS;
}

const U2RenderTarget::FrameStats& U2RenderTarget::getStatistics(void) const
{
    return mStats;
}

float U2RenderTarget::getLastFPS() const
{
    return mStats.lastFPS;
}
float U2RenderTarget::getAverageFPS() const
{
    return mStats.avgFPS;
}
float U2RenderTarget::getBestFPS() const
{
    return mStats.bestFPS;
}
float U2RenderTarget::getWorstFPS() const
{
    return mStats.worstFPS;
}

size_t U2RenderTarget::getTriangleCount(void) const
{
    return mStats.triangleCount;
}

size_t U2RenderTarget::getBatchCount(void) const
{
    return mStats.batchCount;
}

float U2RenderTarget::getBestFrameTime() const
{
    return (float)mStats.bestFrameTime;
}

float U2RenderTarget::getWorstFrameTime() const
{
    return (float)mStats.worstFrameTime;
}

void U2RenderTarget::resetStatistics(void)
{
    mStats.avgFPS = 0.0;
    mStats.bestFPS = 0.0;
    mStats.lastFPS = 0.0;
    mStats.worstFPS = 999.0;
    mStats.triangleCount = 0;
    mStats.batchCount = 0;
    mStats.bestFrameTime = 999999;
    mStats.worstFrameTime = 0;

    mLastTime = mTimer->getMilliseconds();
    mLastSecond = mLastTime;
    mFrameCount = 0;
}

void U2RenderTarget::updateStats(void)
{
    ++mFrameCount;
    unsigned long thisTime = mTimer->getMilliseconds();

    // check frame time
    unsigned long frameTime = thisTime - mLastTime ;
    mLastTime = thisTime ;

    mStats.bestFrameTime = std::min(mStats.bestFrameTime, frameTime);
    mStats.worstFrameTime = std::max(mStats.worstFrameTime, frameTime);

    // check if new second (update only once per second)
    if (thisTime - mLastSecond > 1000) 
    { 
        // new second - not 100% precise
        mStats.lastFPS = (float)mFrameCount / (float)(thisTime - mLastSecond) * 1000.0f;

        if (mStats.avgFPS == 0)
            mStats.avgFPS = mStats.lastFPS;
        else
            mStats.avgFPS = (mStats.avgFPS + mStats.lastFPS) / 2; // not strictly correct, but good enough

        mStats.bestFPS = std::max(mStats.bestFPS, mStats.lastFPS);
        mStats.worstFPS = std::min(mStats.worstFPS, mStats.lastFPS);

        mLastSecond = thisTime ;
        mFrameCount  = 0;

    }

}

void U2RenderTarget::getCustomAttribute(const U2String& name, void* pData)
{
    U2_EXCEPT(U2Exception::ERR_INVALIDPARAMS, "Attribute not found.", "U2RenderTarget::getCustomAttribute");
}
//-----------------------------------------------------------------------
void U2RenderTarget::addListener(RenderTargetListener* listener)
{
    mListeners.push_back(listener);
}
//-----------------------------------------------------------------------
void U2RenderTarget::removeListener(RenderTargetListener* listener)
{
    RenderTargetListenerList::iterator i;
    for (i = mListeners.begin(); i != mListeners.end(); ++i)
    {
        if (*i == listener)
        {
            mListeners.erase(i);
            break;
        }
    }

}
//-----------------------------------------------------------------------
void U2RenderTarget::removeAllListeners(void)
{
    mListeners.clear();
}
//-----------------------------------------------------------------------
void U2RenderTarget::firePreUpdate(void)
{
    RenderTargetEvent evt;
    evt.source = this;

    RenderTargetListenerList::iterator i, iend;
    i = mListeners.begin();
    iend = mListeners.end();
    for(; i != iend; ++i)
    {
        (*i)->preRenderTargetUpdate(evt);
    }


}
//-----------------------------------------------------------------------
void U2RenderTarget::firePostUpdate(void)
{
    RenderTargetEvent evt;
    evt.source = this;

    RenderTargetListenerList::iterator i, iend;
    i = mListeners.begin();
    iend = mListeners.end();
    for(; i != iend; ++i)
    {
        (*i)->postRenderTargetUpdate(evt);
    }
}

//-----------------------------------------------------------------------
unsigned short U2RenderTarget::getNumViewports(void) const
{
    return (unsigned short)mViewportList.size();

}
//-----------------------------------------------------------------------
U2Viewport* U2RenderTarget::getViewport(unsigned short index)
{
    assert (index < mViewportList.size() && "Index out of bounds");

    ViewportList::iterator i = mViewportList.begin();
    while (index--)
        ++i;
    return i->second;
}
//-----------------------------------------------------------------------
U2Viewport* U2RenderTarget::getViewportByZOrder(int ZOrder)
{
	ViewportList::iterator i = mViewportList.find(ZOrder);
	if(i == mViewportList.end())
	{
		U2_EXCEPT(U2Exception::ERR_ITEM_NOT_FOUND,"No viewport with given zorder : "
			+ U2StringConverter::toString(ZOrder), "U2RenderTarget::getViewportByZOrder");
	}
    return i->second;
}
//-----------------------------------------------------------------------
bool U2RenderTarget::hasViewportWithZOrder(int ZOrder)
{
	ViewportList::iterator i = mViewportList.find(ZOrder);
	return i != mViewportList.end();
}

//-----------------------------------------------------------------------
bool U2RenderTarget::isActive() const
{
    return mActive;
}
//-----------------------------------------------------------------------
void U2RenderTarget::setActive( bool state )
{
    mActive = state;
}

//-----------------------------------------------------------------------
void U2RenderTarget::fireViewportPreUpdate(U2Viewport* vp)
{
    RenderTargetViewportEvent evt;
    evt.source = vp;

    RenderTargetListenerList::iterator i, iend;
    i = mListeners.begin();
    iend = mListeners.end();
    for(; i != iend; ++i)
    {
        (*i)->preViewportUpdate(evt);
    }
}
//-----------------------------------------------------------------------
void U2RenderTarget::fireViewportPostUpdate(U2Viewport* vp)
{
    RenderTargetViewportEvent evt;
    evt.source = vp;

    RenderTargetListenerList::iterator i, iend;
    i = mListeners.begin();
    iend = mListeners.end();
    for(; i != iend; ++i)
    {
        (*i)->postViewportUpdate(evt);
    }
}
//-----------------------------------------------------------------------
void U2RenderTarget::fireViewportAdded(U2Viewport* vp)
{
	RenderTargetViewportEvent evt;
	evt.source = vp;

	RenderTargetListenerList::iterator i, iend;
	i = mListeners.begin();
	iend = mListeners.end();
	for(; i != iend; ++i)
	{
		(*i)->viewportAdded(evt);
	}
}
//-----------------------------------------------------------------------
void U2RenderTarget::fireViewportRemoved(U2Viewport* vp)
{
	RenderTargetViewportEvent evt;
	evt.source = vp;

	// Make a temp copy of the listeners
	// some will want to remove themselves as listeners when they get this
	RenderTargetListenerList tempList = mListeners;

	RenderTargetListenerList::iterator i, iend;
	i = tempList.begin();
	iend = tempList.end();
	for(; i != iend; ++i)
	{
		(*i)->viewportRemoved(evt);
	}
}

//-----------------------------------------------------------------------
U2String U2RenderTarget::writeContentsToTimestampedFile(const U2String& filenamePrefix, const U2String& filenameSuffix)
{
    struct tm *pTime;
    time_t ctTime; time(&ctTime);
    pTime = localtime( &ctTime );
    U2StringStream oss;
    oss	<< std::setw(2) << std::setfill('0') << (pTime->tm_mon + 1)
        << std::setw(2) << std::setfill('0') << pTime->tm_mday
        << std::setw(2) << std::setfill('0') << (pTime->tm_year + 1900)
        << "_" << std::setw(2) << std::setfill('0') << pTime->tm_hour
        << std::setw(2) << std::setfill('0') << pTime->tm_min
        << std::setw(2) << std::setfill('0') << pTime->tm_sec
        << std::setw(3) << std::setfill('0') << (mTimer->getMilliseconds() % 1000);
    U2String filename = filenamePrefix + oss.str() + filenameSuffix;
    writeContentsToFile(filename);
    return filename;

}
//-----------------------------------------------------------------------
void U2RenderTarget::writeContentsToFile(const U2String& filename)
{
	PixelFormat pf = suggestPixelFormat();

	u2uchar *data = U2_ALLOC_T(u2uchar, mWidth * mHeight * U2PixelUtil::getNumElemBytes(pf), MEMCATEGORY_RENDERSYS);
	U2PixelBox pb(mWidth, mHeight, 1, pf, data);

	copyContentsToMemory(pb);

	U2Image().loadDynamicImage(data, mWidth, mHeight, 1, pf, false, 1, 0).save(filename);

	U2_FREE(data, MEMCATEGORY_RENDERSYS);
}

//-----------------------------------------------------------------------
void U2RenderTarget::_notifyCameraRemoved(const U2Camera* cam)
{
    ViewportList::iterator i, iend;
    iend = mViewportList.end();
    for (i = mViewportList.begin(); i != iend; ++i)
    {
        U2Viewport* v = i->second;
        if (v->getCamera() == cam)
        {
            // disable camera link
            v->setCamera(0);
        }
    }
}

//-----------------------------------------------------------------------
void U2RenderTarget::setAutoUpdated(bool autoup)
{
    mAutoUpdate = autoup;
}
//-----------------------------------------------------------------------
bool U2RenderTarget::isAutoUpdated(void) const
{
    return mAutoUpdate;
}
//-----------------------------------------------------------------------
bool U2RenderTarget::isPrimary(void) const
{
    // RenderWindow will override and return true for the primary window
    return false;
}
//-----------------------------------------------------------------------
U2RenderTarget::Impl *U2RenderTarget::_getImpl()
{
    return 0;
}
//-----------------------------------------------------------------------
void U2RenderTarget::update(bool swap)
{
    // call implementation
    updateImpl();


	if (swap)
	{
		// Swap buffers
	    swapBuffers(U2Root::getSingleton().getRenderSystem()->getWaitForVerticalBlank());
	}
}

